package com.wngbms.websocket.user;

import com.wngbms.websocket.entity.UserDelay;
import com.wngbms.websocket.entity.UserMetaInfo;
import com.wngbms.websocket.user.RedisUserMetaInfoHandler;
import com.wngbms.websocket.zookeeper.UserNodeNotifier;
import com.wngbms.websocket.zookeeper.ZkConst;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class UserHook implements ZkConst {

    private static final Map<String, Session> userSessionMap = new ConcurrentHashMap<>();
    private static final Map<String, UserDelay> userDelayQueueMap = new ConcurrentHashMap<>();

    @Value("${server.port}")
    short port;
    @Value("${server.address}")
    String serverIp;

    @Autowired
    UserNodeNotifier userNodeNotifier;
    @Autowired
    RedisUserMetaInfoHandler redisUserMetaInfoHandler;

    public void doEvent(String eventId, String userId) {
        log.info("{} do something eventId:{}", userId, eventId);

        Session session = userSessionMap.get(userId);
        if (session == null) {
            log.error("用户:{}, session不存在", userId);
            return;
        }

        try {
            /**
             * java.lang.IllegalStateException:
             *    The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
             * websocket推送数据的方法有：
             *                session.getBasicRemote().sendText(message);  //同步发送
             *                session.getAsyncRemote().sendText(message); //异步发送
             * 经过测试，在高并发的情况下，两种发送方法都会抛出上面的异常。查了很多博客，
             * 发现是多个线程同时使用同一session发送的原因。决定做如下修改。
             */
            synchronized (session) {
                RemoteEndpoint.Basic basicRemote = session.getBasicRemote();
                basicRemote.sendText("event happen..." + eventId);
            }
        } catch (IOException e) {
            log.error("send error", e);
        }
    }

    public void putUserSession(String userId, Session session) {
        UserMetaInfo existUserMeta = redisUserMetaInfoHandler.getUserMetaInfo(userId);
        // serviceId不为空把用户踢下线
        if (existUserMeta != null) {
            if (SERVICE_UUID.equals(existUserMeta.getServiceId())) {
                log.debug("Kick off the line by myself");
                closeUserSession(userId);
            } else {
                log.debug("Kick off the line to others");
                userNodeNotifier.forceCloseRemoteUser(existUserMeta);
            }
        }

        UserMetaInfo userMetaInfo = UserMetaInfo.builder()
                .ip(serverIp).port(port).serviceId(SERVICE_UUID)
                .userId(userId).build();
        redisUserMetaInfoHandler.setUserMetaInfo(userMetaInfo);
        userSessionMap.put(userId, session);
    }

    /**
     * 同一账号只能在一个设备上登录,
     * 如果在多个设备上登录, 需要把之前的登录的设备踢下线
     *
     * @param userId
     */
    public void closeUserSession(String userId) {
        Session existSession = userSessionMap.get(userId);
        log.info("remove same session userId:{}, session:{}", userId, existSession);
        if (existSession != null) {
            forceCloseUserSession(userId, existSession);
            removeUserSession(userId, existSession);
        }
    }

    public void forceCloseUserSession(String userId, Session session) {
        try {
            session.getBasicRemote().sendText("you were forced closed");
            session.close();
        } catch (IOException e) {
            log.error("send error", e);
        }
    }

    public void removeUserSession(String userId, Session session) {
        redisUserMetaInfoHandler.deleteUserMetaInfo(userId);
        userSessionMap.remove(userId);
    }

    public void updateUserInfo(String userId) {
        UserMetaInfo userMetaInfo = UserMetaInfo.builder()
                .ip(serverIp).port(port).serviceId(SERVICE_UUID)
                .userId(userId).build();
        redisUserMetaInfoHandler.setUserMetaInfo(userMetaInfo);
    }

}
